﻿/*
 * @author: wizardc
 */

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

namespace DouEditor.Extend
{
    public sealed class AnimatorEditorPlayMgr
    {
        private static AnimatorEditorPlayMgr _instance;
        public static AnimatorEditorPlayMgr instance => _instance ??= new AnimatorEditorPlayMgr();

        private Dictionary<AnimationEditorData, AnimatorEditorPlayInfo> _map = new Dictionary<AnimationEditorData, AnimatorEditorPlayInfo>();
        private double _prevTime;

        private AnimatorEditorPlayMgr()
        {
            _prevTime = EditorApplication.timeSinceStartup;

            EditorApplication.update -= OnEditorUpdate;
            EditorApplication.update += OnEditorUpdate;
        }

        public AnimatorEditorPlayInfo AddData(AnimationEditorData editorData)
        {
            RemoveData(editorData);
            var info = new AnimatorEditorPlayInfo(editorData);
            _map.Add(editorData, info);
            return info;
        }

        public AnimatorEditorPlayInfo Get(AnimationEditorData editorData)
        {
            if (_map.ContainsKey(editorData))
            {
                return _map[editorData];
            }
            return null;
        }

        public void RemoveData(AnimationEditorData editorData)
        {
            if (_map.ContainsKey(editorData))
            {
                var info = _map[editorData];
                info.Destroy();
                _map.Remove(editorData);
            }
        }

        private void OnEditorUpdate()
        {
            var now = EditorApplication.timeSinceStartup;
            var passedTime = now - _prevTime;
            _prevTime = now;
            foreach (var kv in _map)
            {
                kv.Value.Update(passedTime);
            }
        }
    }

    public class AnimationEditorData
    {
        public static AnimationEditorData Collect(Animator animator)
        {
            var data = new AnimationEditorData();
            data.animator = animator;
            var runtimeController = animator.runtimeAnimatorController;
            var path = AssetDatabase.GetAssetPath(runtimeController);
            var controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(path);
            foreach (var layer in controller.layers)
            {
                foreach (var child in layer.stateMachine.states)
                {
                    var state = child.state;
                    var isDefault = layer.stateMachine.defaultState == state;
                    if (state.motion != null && state.motion is AnimationClip)
                    {
                        var clip = state.motion as AnimationClip;
                        data.clips.Add(new AnimationEditorClipInfo
                        {
                            name = state.name,
                            layer = layer.name,
                            defaultState = isDefault,
                            clip = clip,
                        });
                    }
                }
            }
            return data;
        }

        public Animator animator;
        public List<AnimationEditorClipInfo> clips = new List<AnimationEditorClipInfo>();
    }

    [Serializable]
    public class AnimationEditorClipInfo
    {
        public string name;
        public string layer;
        public bool defaultState; // 是否是默认状态
        public AnimationClip clip;
    }

    public class AnimatorEditorPlayInfo
    {
        private AnimationEditorData _data;

        private PlayableGraph _graph;
        private AnimationPlayableOutput _output;
        private AnimationLayerMixerPlayable? _playable;
        private List<AnimationClipPlayable> _clipPlayableList = new List<AnimationClipPlayable>();
        private List<AnimationClip> _clipList = new List<AnimationClip>();

        private bool _playing;
        private double _currentTime;
        private double _totalTime;
        private bool _isLoop = false;
        private bool _timeGoesOn = false;
        private float _ratio = 1;

        public AnimationEditorData data => _data;

        public bool playing => _playing;

        public double currentTime
        {
            set => _currentTime = value;
            get => _currentTime;
        }

        public double totalTime => _totalTime;

        public bool isLoop
        {
            set => _isLoop = value;
            get => _isLoop;
        }

        /// <summary>
        /// 非循环模式下，时间到达后是否继续计时
        /// </summary>
        public bool timeGoesOn
        {
            set => _timeGoesOn = value;
            get => _timeGoesOn;
        }

        public float ratio
        {
            set => _ratio = value;
            get => _ratio;
        }

        public AnimatorEditorPlayInfo(AnimationEditorData data)
        {
            _data = data;
            _graph = PlayableGraph.Create(data.animator.name);
            _graph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
            _output = AnimationPlayableOutput.Create(_graph, "AnimationOutput", data.animator);
            SwitchState(); // 切入默认状态机
        }

        public void Update(double passTime)
        {
            if (_playing)
            {
                _currentTime += passTime * _ratio;
                if (_currentTime >= _totalTime)
                {
                    if (_isLoop)
                    {
                        _currentTime = _currentTime % _totalTime;
                    }
                    else if (!_timeGoesOn)
                    {
                        _currentTime = _totalTime;
                    }
                }
            }

            if (!_playable.HasValue)
            {
                return;
            }

            // 直接调整动画时间
            _playable.Value.SetTime(_currentTime);
            foreach (var clipPlayable in _clipPlayableList)
            {
                clipPlayable.SetTime(_currentTime);
            }

            _graph.Evaluate();
        }

        private void SwitchState(string name = null)
        {
            if (_playable.HasValue)
            {
                _clipPlayableList.Clear();
                _graph.DestroyPlayable(_playable.Value);
                _playable = null;
            }

            foreach (var info in _data.clips)
            {
                if (name != null)
                {
                    if (info.name == name)
                    {
                        // 不同层相同名称也一起播放
                        _clipList.Add(info.clip);
                    }
                }
                else
                {
                    if (info.defaultState)
                    {
                        _clipList.Add(info.clip);
                    }
                }
            }

            var layerMixer = AnimationLayerMixerPlayable.Create(_graph, _clipList.Count);
            for (int i = 0; i < _clipList.Count; i++)
            {
                _totalTime = Mathf.Max((float) _totalTime, _clipList[i].length);
                var clipPlayable = AnimationClipPlayable.Create(_graph, _clipList[i]);
                _clipPlayableList.Add(clipPlayable);
                _graph.Connect(clipPlayable, 0, layerMixer, i);
            }

            _output.SetSourcePlayable(layerMixer);
            layerMixer.SetInputWeight(0, 1);
            if (_clipList.Count > 1)
            {
                layerMixer.SetInputWeight(1, 1);
            }

            _clipList.Clear();

            _playable = layerMixer;
        }

        /// <param name="name">为空时播放默认状态</param>
        public void Play(string name = null)
        {
            Stop();
            SwitchState(name);
            _playing = true;
            _currentTime = 0;
            // _graph.Play(); // 直接控制时间不需要调用该方法了
        }

        public void Pause()
        {
            _playing = false;
        }

        public void Resume()
        {
            _playing = true;
        }

        public void Stop()
        {
            _playing = false;
            _currentTime = 0;
            // _graph.Stop(); // 直接控制时间不需要调用该方法了
        }

        public void Destroy()
        {
            _graph.Destroy();
        }
    }
}
